按值传递参数时，原则上必须复制每个参数，每个参数都成为所传递实参的副本。对于类，作为副本创建的对象通常由复制构造函数初始化。

调用复制构造函数的代价可能会很高。然而，即使在按值传递参数时，也有方法来避免复制:编译器可能会优化复制对象的复制操作，并且通过移动语义，对复杂对象的操作也可以变得廉价。

来看一个实现的简单函数模板，参数通过值传递:

\begin{lstlisting}[style=styleCXX]
template<typename T>
void printV (T arg) {
	...
}
\end{lstlisting}

为整数调用函数模板时，结果代码为

\begin{lstlisting}[style=styleCXX]
void printV (int arg) {
	...
}
\end{lstlisting}

参数arg成为传入参数的副本，无论它是对象、文字还是函数返回的值。

若定义一个std::string类型，并为此调用函数模板:

\begin{lstlisting}[style=styleCXX]
std::string s = "hi";
printV(s);
\end{lstlisting}

模板参数T实例化为std::string，得到

\begin{lstlisting}[style=styleCXX]
void printV (std::string arg)
{
	...
}
\end{lstlisting}

传递字符串时，arg变成了s的副本。这次的副本是由string类的复制构造函数创建的，这是一个昂贵的操作，因为这个复制操作创建了一个完整的“深”副本，以便该副本内部分配自己的内存来保存该值。

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}string类的实现本身可能进行了一些优化，以降低复制成本。一种是小字符串优化(SSO)，只要值不太长，就直接使用对象内部的一些内存来保存值，而不分配内存。另一种是写时复制优化，只要源文件和副本都没有修改，就会使用与源文件相同的内存创建副本。但是，写时复制优化在多线程代码中有明显的缺陷。由于这个原因，C++11的标准字符串是禁止使用写时复制优化。
\end{tcolorbox}

但是，复制构造函数并不总调用。考虑以下代码:

\begin{lstlisting}[style=styleCXX]
std::string returnString();
std::string s = "hi";
printV(s); // copy constructor
printV(std::string("hi")); // copying usually optimized away (if not, move constructor)
printV(returnString()); // copying usually optimized away (if not, move constructor)
printV(std::move(s)); // move constructor
\end{lstlisting}

第一次调用中，传递了一个左值，使用了复制构造函数。然而，第二次和第三次调用中，当直接调用函数模板来获取prvalues(动态创建或由另一个函数返回的临时对象;参见附录B)，编译器通常会优化传递参数，这样就不会调用复制构造函数了。C++17起，这种优化是必需的。C++17前，不能优化复制的编译器，至少需要使用移动语义，这通常会使复制成本降低。最后一次调用中，当传递xvalue(一个现有的带有std::move()的非常量对象)时，不再需要s的值来强制使用移动构造函数。

因此，可以调用printV()的实现，来声明按值传递的参数，通常只在传递左值(之前创建的对象，因为没有使用std::move()来传递它，所以在之后仍然可用)时才会很昂贵。不幸的是，这是一个常见的情况。在早期创建对象时，在稍后(经过一些修改)将其传递给其他函数的是常规操作。

\hspace*{\fill} \\ %插入空行
\noindent
\textbf{值传递的类型衰变}

还有一个按值传递的属性:当按值传递参数时，类型会衰变。从而数组将转换为指针，并删除const和volatile等限定符(就像使用值作为使用auto声明的对象的初始化式一样):

\begin{tcolorbox}[colback=webgreen!5!white,colframe=webgreen!75!black]
\hspace*{0.75cm}术语衰变来自C语言，也适用于从函数到函数指针的类型转换(参见11.1.1节)。
\end{tcolorbox}

\begin{lstlisting}[style=styleCXX]
template<typename T>
void printV (T arg) {
	...
}

std::string const c = "hi";
printV(c); // c decays so that arg has type std::string

printV("hi"); // decays to pointer so that arg has type char const*

int arr[4];
printV(arr); // decays to pointer so that arg has type int*
\end{lstlisting}

因此，当传递字符串字面值"hi"时，类型char const[3]衰变为char const*，从而成为T的推导类型。

\begin{lstlisting}[style=styleCXX]
void printV (char const* arg)
{
	...
}
\end{lstlisting}

这种行为有利有弊，简化了对传递的字符串字面量的处理，但缺点是在printV()中，无法区分传递单个元素的指针和传递数组。因此，将在7.4节中将讨论如何处理字符串字面值和其他数组。




